├── .gitignore ├── .htaccess ├── app ├── Bootstrap.php ├── Core │ └── RouterFactory.php ├── Model │ └── PostFacade.php └── Presentation │ ├── @layout.latte │ ├── Accessory │ └── LatteExtension.php │ ├── Edit │ ├── EditPresenter.php │ ├── create.latte │ └── edit.latte │ ├── Error │ ├── Error4xx │ │ ├── 403.latte │ │ ├── 404.latte │ │ ├── 410.latte │ │ ├── 4xx.latte │ │ └── Error4xxPresenter.php │ └── Error5xx │ │ ├── 500.phtml │ │ ├── 503.phtml │ │ └── Error5xxPresenter.php │ ├── Home │ ├── HomePresenter.php │ └── default.latte │ ├── Post │ ├── PostPresenter.php │ └── show.latte │ └── Sign │ ├── SignPresenter.php │ └── in.latte ├── assets └── main.js ├── bin └── .gitignore ├── composer.json ├── config ├── common.neon └── services.neon ├── data └── blog.sqlite ├── database.sql ├── log └── .gitignore ├── package.json ├── phpstan.neon ├── readme.md ├── temp └── .gitignore ├── vite.config.ts └── www ├── .htaccess ├── assets └── style.css ├── favicon.ico ├── index.php └── robots.txt /.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nette-examples/quickstart/4db1bf9a635c394b2fa0056fcfb27bc83e0364f1/.gitignore -------------------------------------------------------------------------------- /.htaccess: -------------------------------------------------------------------------------- 1 | Require all denied 2 | -------------------------------------------------------------------------------- /app/Bootstrap.php: -------------------------------------------------------------------------------- 1 | rootDir = dirname(__DIR__); 23 | 24 | // The configurator is responsible for setting up the application environment and services. 25 | // Learn more at https://doc.nette.org/en/bootstrap 26 | $this->configurator = new Configurator; 27 | 28 | // Set the directory for temporary files generated by Nette (e.g. compiled templates) 29 | $this->configurator->setTempDirectory($this->rootDir . '/temp'); 30 | } 31 | 32 | 33 | public function bootWebApplication(): Nette\DI\Container 34 | { 35 | $this->initializeEnvironment(); 36 | $this->setupContainer(); 37 | return $this->configurator->createContainer(); 38 | } 39 | 40 | 41 | public function initializeEnvironment(): void 42 | { 43 | // Nette is smart, and the development mode turns on automatically, 44 | // or you can enable for a specific IP address it by uncommenting the following line: 45 | // $this->configurator->setDebugMode('secret@23.75.345.200'); 46 | 47 | // Enables Tracy: the ultimate "swiss army knife" debugging tool. 48 | // Learn more about Tracy at https://tracy.nette.org 49 | $this->configurator->enableTracy($this->rootDir . '/log'); 50 | 51 | // RobotLoader: autoloads all classes in the given directory 52 | $this->configurator->createRobotLoader() 53 | ->addDirectory(__DIR__) 54 | ->register(); 55 | } 56 | 57 | 58 | private function setupContainer(): void 59 | { 60 | // Load configuration files 61 | $configDir = $this->rootDir . '/config'; 62 | $this->configurator->addConfig($configDir . '/common.neon'); 63 | $this->configurator->addConfig($configDir . '/services.neon'); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /app/Core/RouterFactory.php: -------------------------------------------------------------------------------- 1 | addRoute('/[/]', 'Home:default'); 23 | return $router; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/Model/PostFacade.php: -------------------------------------------------------------------------------- 1 | database 31 | ->table('posts') 32 | ->where('created_at < ', new \DateTime) 33 | ->order('created_at DESC'); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /app/Presentation/@layout.latte: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | {* If a title block is defined in the child template, it's included here. *} 8 | {ifset title}{include title|stripHtml} | {/ifset}Nette Framework Micro-blog 9 | 10 | 11 | 12 | 13 | 14 | 22 | 23 | {* Display any flash messages to the user *} 24 |
{$flash->message}
25 | 26 | {* Main content placeholder. This will be replaced by the content block of child templates. *} 27 | {include content} 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /app/Presentation/Accessory/LatteExtension.php: -------------------------------------------------------------------------------- 1 | getUser()->isLoggedIn()) { 33 | $this->redirect('Sign:in'); 34 | } 35 | } 36 | 37 | 38 | /** 39 | * Render the edit view for a specific post. 40 | * Sets default values for the form based on the post data. 41 | */ 42 | public function renderEdit(int $id): void 43 | { 44 | $post = $this->database 45 | ->table('posts') 46 | ->get($id); 47 | 48 | if (!$post) { 49 | $this->error('Post not found'); 50 | } 51 | 52 | $this->getComponent('postForm') 53 | ->setDefaults($post->toArray()); 54 | } 55 | 56 | 57 | /** 58 | * Form for editing/creating posts. 59 | */ 60 | protected function createComponentPostForm(): Form 61 | { 62 | $form = new Form; 63 | $form->addText('title', 'Title:') 64 | ->setRequired(); 65 | $form->addTextArea('content', 'Content:') 66 | ->setRequired(); 67 | 68 | $form->addSubmit('send', 'Save and publish'); 69 | $form->onSuccess[] = $this->postFormSucceeded(...); 70 | 71 | return $form; 72 | } 73 | 74 | 75 | /** 76 | * Handles the successful submission of the post form. 77 | * Updates an existing post or creates a new one. 78 | */ 79 | private function postFormSucceeded(array $data): void 80 | { 81 | $id = $this->getParameter('id'); 82 | 83 | if ($id) { 84 | $post = $this->database 85 | ->table('posts') 86 | ->get($id); 87 | $post->update($data); 88 | 89 | } else { 90 | $post = $this->database 91 | ->table('posts') 92 | ->insert($data); 93 | } 94 | 95 | $this->flashMessage('Post was published', 'success'); 96 | $this->redirect('Post:show', $post->id); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /app/Presentation/Edit/create.latte: -------------------------------------------------------------------------------- 1 | {block content} 2 |

New post

3 | 4 | {control postForm} 5 | -------------------------------------------------------------------------------- /app/Presentation/Edit/edit.latte: -------------------------------------------------------------------------------- 1 | {block content} 2 |

Edit post

3 | 4 | {control postForm} 5 | -------------------------------------------------------------------------------- /app/Presentation/Error/Error4xx/403.latte: -------------------------------------------------------------------------------- 1 | {block content} 2 |

Access Denied

3 | 4 |

You do not have permission to view this page. Please try contact the web 5 | site administrator if you believe you should be able to view this page.

6 | 7 |

error 403

8 | -------------------------------------------------------------------------------- /app/Presentation/Error/Error4xx/404.latte: -------------------------------------------------------------------------------- 1 | {block content} 2 |

Page Not Found

3 | 4 |

The page you requested could not be found. It is possible that the address is 5 | incorrect, or that the page no longer exists. Please use a search engine to find 6 | what you are looking for.

7 | 8 |

error 404

9 | -------------------------------------------------------------------------------- /app/Presentation/Error/Error4xx/410.latte: -------------------------------------------------------------------------------- 1 | {block content} 2 |

Page Not Found

3 | 4 |

The page you requested has been taken off the site. We apologize for the inconvenience.

5 | 6 |

error 410

7 | -------------------------------------------------------------------------------- /app/Presentation/Error/Error4xx/4xx.latte: -------------------------------------------------------------------------------- 1 | {block content} 2 |

Oops...

3 | 4 |

Your browser sent a request that this server could not understand or process.

5 | 6 |

error {$httpCode}

7 | -------------------------------------------------------------------------------- /app/Presentation/Error/Error4xx/Error4xxPresenter.php: -------------------------------------------------------------------------------- 1 | getCode(); 21 | $file = is_file($file = __DIR__ . "/$code.latte") 22 | ? $file 23 | : __DIR__ . '/4xx.latte'; 24 | $this->template->httpCode = $code; 25 | $this->template->setFile($file); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/Presentation/Error/Error5xx/500.phtml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Server Error 5 | 6 | 13 | 14 |
15 |
16 |

Server Error

17 | 18 |

We're sorry! The server encountered an internal error and 19 | was unable to complete your request. Please try again later.

20 | 21 |

error 500

22 |
23 |
24 | 25 | 28 | -------------------------------------------------------------------------------- /app/Presentation/Error/Error5xx/503.phtml: -------------------------------------------------------------------------------- 1 | 9 | 10 | 11 | 12 | 13 | 14 | 19 | 20 | Site is temporarily down for maintenance 21 | 22 |

We're Sorry

23 | 24 |

The site is temporarily down for maintenance. Please try again in a few minutes.

25 | -------------------------------------------------------------------------------- /app/Presentation/Error/Error5xx/Error5xxPresenter.php: -------------------------------------------------------------------------------- 1 | getParameter('exception'); 33 | $this->logger->log($exception, ILogger::EXCEPTION); 34 | 35 | // Display a generic error message to the user 36 | return new Responses\CallbackResponse(function (Http\IRequest $httpRequest, Http\IResponse $httpResponse): void { 37 | if (preg_match('#^text/html(?:;|$)#', (string) $httpResponse->getHeader('Content-Type'))) { 38 | require __DIR__ . '/500.phtml'; 39 | } 40 | }); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /app/Presentation/Home/HomePresenter.php: -------------------------------------------------------------------------------- 1 | template->posts = $this->facade 31 | ->getPublicArticles() 32 | ->limit(5); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /app/Presentation/Home/default.latte: -------------------------------------------------------------------------------- 1 | {block content} 2 |

My awesome blog

3 | 4 |
5 |
{$post->created_at|date:'F j, Y'}
6 | 7 |

{$post->title}

8 | 9 |
{$post->content}
10 |
11 | {/block} 12 | -------------------------------------------------------------------------------- /app/Presentation/Post/PostPresenter.php: -------------------------------------------------------------------------------- 1 | database->table('posts')->get($id); 30 | if (!$post) { 31 | $this->error('Post not found'); 32 | } 33 | 34 | $this->template->post = $post; 35 | $this->template->comments = $post->related('comment')->order('created_at'); 36 | } 37 | 38 | 39 | /** 40 | * Form for adding comments to a post. 41 | */ 42 | protected function createComponentCommentForm(): Form 43 | { 44 | $form = new Form; 45 | $form->addText('name', 'Your name:') 46 | ->setRequired(); 47 | 48 | $form->addEmail('email', 'Email:'); 49 | 50 | $form->addTextArea('content', 'Comment:') 51 | ->setRequired(); 52 | 53 | $form->addSubmit('send', 'Publish comment'); 54 | $form->onSuccess[] = $this->commentFormSucceeded(...); 55 | 56 | return $form; 57 | } 58 | 59 | 60 | /** 61 | * Handles the successful submission of the comment form. 62 | */ 63 | private function commentFormSucceeded(\stdClass $data): void 64 | { 65 | $this->database->table('comments')->insert([ 66 | 'post_id' => $this->getParameter('id'), 67 | 'name' => $data->name, 68 | 'email' => $data->email, 69 | 'content' => $data->content, 70 | ]); 71 | 72 | $this->flashMessage('Thank you for your comment', 'success'); 73 | $this->redirect('this'); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /app/Presentation/Post/show.latte: -------------------------------------------------------------------------------- 1 | {block content} 2 |
{$post->created_at|date:'F j, Y'}
3 | 4 |

{$post->title}

5 | 6 |
{$post->content}
7 | 8 |

Comments

9 | 10 |
11 | {foreach $comments as $comment} 12 |

{$comment->name} said:

13 |
{$comment->content}
14 | {/foreach} 15 |
16 | 17 |

Post new comment

18 | 19 | {control commentForm} 20 | -------------------------------------------------------------------------------- /app/Presentation/Sign/SignPresenter.php: -------------------------------------------------------------------------------- 1 | addText('username', 'Username:') 23 | ->setRequired('Please enter your username.'); 24 | 25 | $form->addPassword('password', 'Password:') 26 | ->setRequired('Please enter your password.'); 27 | 28 | $form->addSubmit('send', 'Sign in'); 29 | 30 | // call method signInFormSucceeded() on success 31 | $form->onSuccess[] = $this->signInFormSucceeded(...); 32 | return $form; 33 | } 34 | 35 | 36 | /** 37 | * Handles the successful submission of the sign-in form. 38 | */ 39 | private function signInFormSucceeded(Form $form, \stdClass $data): void 40 | { 41 | try { 42 | $this->getUser()->login($data->username, $data->password); 43 | $this->redirect('Home:'); 44 | 45 | } catch (Nette\Security\AuthenticationException $e) { 46 | $form->addError('Incorrect username or password.'); 47 | } 48 | } 49 | 50 | 51 | /** 52 | * Handles user sign out and redirects to the homepage. 53 | */ 54 | public function actionOut(): void 55 | { 56 | $this->getUser()->logout(); 57 | $this->flashMessage('You have been signed out.'); 58 | $this->redirect('Home:'); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /app/Presentation/Sign/in.latte: -------------------------------------------------------------------------------- 1 | {block content} 2 |

Sign in

3 | 4 | {control signInForm} 5 | -------------------------------------------------------------------------------- /assets/main.js: -------------------------------------------------------------------------------- 1 | // Initialize Nette Forms on page load 2 | import netteForms from 'nette-forms'; 3 | 4 | netteForms.initOnLoad(); 5 | -------------------------------------------------------------------------------- /bin/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nette/web-project", 3 | "description": "Nette: Standard Web Project", 4 | "keywords": ["nette"], 5 | "type": "project", 6 | "license": ["MIT", "BSD-3-Clause", "GPL-2.0-only", "GPL-3.0-only"], 7 | "require": { 8 | "php": ">= 8.1", 9 | "nette/application": "^3.2.3", 10 | "nette/assets": "^1.0.0", 11 | "nette/bootstrap": "^3.2.6", 12 | "nette/caching": "^3.2", 13 | "nette/database": "^3.2", 14 | "nette/di": "^3.2", 15 | "nette/forms": "^3.2", 16 | "nette/http": "^3.3", 17 | "nette/mail": "^4.0", 18 | "nette/robot-loader": "^4.0", 19 | "nette/security": "^3.2", 20 | "nette/utils": "^4.0", 21 | "latte/latte": "^3.0", 22 | "tracy/tracy": "^2.10" 23 | }, 24 | "require-dev": { 25 | "nette/tester": "^2.5", 26 | "phpstan/phpstan-nette": "^2", 27 | "symfony/thanks": "^1" 28 | }, 29 | "autoload": { 30 | "psr-4": { 31 | "App\\": "app" 32 | } 33 | }, 34 | "scripts": { 35 | "phpstan": "phpstan analyse", 36 | "tester": "tester tests -s" 37 | }, 38 | "minimum-stability": "stable", 39 | "config": { 40 | "allow-plugins": { 41 | "symfony/thanks": true 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /config/common.neon: -------------------------------------------------------------------------------- 1 | # see https://doc.nette.org/en/configuring 2 | 3 | parameters: 4 | 5 | 6 | application: 7 | # Error presenters to handle application errors 8 | errorPresenter: 9 | 4xx: Error:Error4xx 10 | 5xx: Error:Error5xx 11 | # Presenter to class name mapping pattern 12 | mapping: App\Presentation\*\**Presenter 13 | 14 | 15 | database: 16 | dsn: 'sqlite:%rootDir%/data/blog.sqlite' 17 | user: 18 | password: 19 | 20 | 21 | security: 22 | users: 23 | admin: secret # user 'admin', password 'secret' 24 | 25 | 26 | latte: 27 | strictTypes: yes 28 | assets: 29 | mapping: 30 | default: 31 | path: assets 32 | # type: vite # Uncomment to activate Vite for asset building -------------------------------------------------------------------------------- /config/services.neon: -------------------------------------------------------------------------------- 1 | # Service registrations. See https://doc.nette.org/dependency-injection/services 2 | 3 | services: 4 | - App\Core\RouterFactory::createRouter 5 | 6 | 7 | search: 8 | - in: %appDir% 9 | classes: 10 | - *Facade 11 | - *Factory 12 | - *Repository 13 | - *Service 14 | -------------------------------------------------------------------------------- /data/blog.sqlite: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nette-examples/quickstart/4db1bf9a635c394b2fa0056fcfb27bc83e0364f1/data/blog.sqlite -------------------------------------------------------------------------------- /database.sql: -------------------------------------------------------------------------------- 1 | 2 | SET NAMES utf8; 3 | SET foreign_key_checks = 0; 4 | 5 | CREATE TABLE `comments` ( 6 | `id` int(11) NOT NULL AUTO_INCREMENT, 7 | `post_id` int(11) NOT NULL, 8 | `name` varchar(255) DEFAULT NULL, 9 | `email` varchar(255) DEFAULT NULL, 10 | `content` text NOT NULL, 11 | `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, 12 | PRIMARY KEY (`id`), 13 | KEY `post_id` (`post_id`), 14 | CONSTRAINT `comments_ibfk_1` FOREIGN KEY (`post_id`) REFERENCES `posts` (`id`) 15 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 16 | 17 | 18 | CREATE TABLE `posts` ( 19 | `id` int(11) NOT NULL AUTO_INCREMENT, 20 | `title` varchar(255) NOT NULL, 21 | `content` text NOT NULL, 22 | `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, 23 | PRIMARY KEY (`id`) 24 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 25 | 26 | 27 | -- sample data 28 | 29 | INSERT INTO posts (id, title, content, created_at) VALUES 30 | (1, 'Discovering the Eccentric Vogons', 'Vogons are known not just for their bureaucratic obsession but also for their dreadful poetry. This post explores the peculiar culture of Vogons, their love for paperwork, and why they are considered the third worst poets in the Universe.', '2008-05-01 12:00:00'), 31 | (2, 'The Secrets of the Pan Galactic Gargle Blaster', 'The Pan Galactic Gargle Blaster is known as the best drink in existence. This post delves into its zesty components, the effects of its consumption, and why it should be drunk with caution. Remember, the effect is similar to having your brains smashed out by a slice of lemon wrapped round a large gold brick.', '2008-04-01 12:00:00'), 32 | (3, 'Marvin’s Guide to Coping with Existence', 'Life? Don’t talk to me about life! This post explores Marvin the Paranoid Android’s views on existence, detailing his journey through space with his depressingly funny insights and why his processors ache so much.', '2008-03-01 12:00:00'), 33 | (4, 'Exploring the Infinite Improbability Drive', 'The Infinite Improbability Drive is a wonderful new method of crossing vast interstellar distances in a mere nothingth of a second without all that tedious mucking about in hyperspace. This post explains how this remarkable technology works and the bizarre occurrences that happen when it’s activated.', '2008-06-01 12:00:00'), 34 | (5, 'Why Earth is Mostly Harmless', 'According to "The Hitchhiker’s Guide to the Galaxy", Earth is described as mostly harmless. This post examines what led to such a minimalistic description and what implications it might have for future intergalactic hitchhikers looking for a quick guide to the galaxy.', '2008-02-01 12:00:00'); 35 | 36 | INSERT INTO comments (post_id, name, email, content, created_at) VALUES 37 | (1, 'Zaphod Beeblebrox', NULL, 'Never could get the hang of Vogons. Tried to party with them once, but all they wanted to do was file reports. What a snooze fest!', '2008-06-15 14:23:52'), 38 | (1, 'Arthur Dent', NULL, 'I had a particularly dreadful encounter with a Vogon poem once. It was worse than being strapped to a mind-evaporating machine. The horror!', '2008-08-20 09:17:31'), 39 | (1, 'Ford Prefect', NULL, 'Vogons might have their quirks, but you have to admit, they’re remarkably consistent. Consistently terrible, but consistent nonetheless.', '2009-02-11 22:30:45'), 40 | (2, 'Trillian', NULL, 'One should be cautious with the Pan Galactic Gargle Blaster. It’s not for the faint-hearted. I prefer something less... explosive.', '2008-05-23 18:45:19'), 41 | (2, 'Arthur Dent', NULL, 'Tried it once, thought I was a sofa for a week. Never again.', '2009-07-08 16:54:07'), 42 | (2, 'Ford Prefect', NULL, 'If you want to understand the universe, start with a Pan Galactic Gargle Blaster. Or end with one. Either way, it’s a profound experience.', '2008-12-30 20:11:53'), 43 | (3, 'Slartibartfast', NULL, 'Poor Marvin, I do feel for him sometimes. But then I remember it’s better him than me!', '2008-04-14 12:22:33'), 44 | (3, 'Zaphod Beeblebrox', NULL, 'Marvin’s got the best understanding of life. Always expect the worst, and you’ll never be disappointed!', '2009-05-19 14:33:21'), 45 | (3, 'Trillian', NULL, 'It’s quite sad, really. If Marvin were a planet, he’d be the rainiest one in the solar system.', '2008-11-23 09:45:12'), 46 | (3, 'Arthur Dent', NULL, 'Sometimes I think Marvin secretly enjoys his misery. It’s what keeps him charged.', '2009-08-17 11:37:08'), 47 | (4, 'Arthur Dent', NULL, 'The first time we used the Infinite Improbability Drive, I turned into a penguin. It’s hard to operate a spaceship with flippers, let me tell you.', '2008-07-30 10:27:43'), 48 | (4, 'Ford Prefect', NULL, 'I love the unpredictability of the Improbability Drive. It’s like throwing caution to the wind, but with quantum physics!', '2009-03-26 13:12:59'), 49 | (4, 'Zaphod Beeblebrox', NULL, 'The best part about the Drive is that it’s completely safe. Well, mostly. Okay, sometimes. Alright, at least it’s not boring!', '2009-01-15 15:01:22'), 50 | (5, 'Trillian', NULL, 'Mostly harmless? More like mostly ignored. Earth has its charms, but they’re subtle and often missed by the galactic crowd.', '2008-03-29 08:21:34'), 51 | (5, 'Slartibartfast', NULL, 'I’ve always had a soft spot for Earth. The fjords I designed are particularly delightful. It’s a shame not many appreciate the effort.', '2008-10-18 17:14:19'), 52 | (5, 'Marvin', NULL, 'Harmless? I’ve seen more action there than in most places. It’s the boredom that’s harmful, trust me.', '2009-04-25 19:58:11'), 53 | (5, 'Ford Prefect', NULL, 'It’s the quiet ones you have to watch. Earth might seem harmless, but it’s full of surprises.', '2009-07-03 22:33:47'); 54 | -------------------------------------------------------------------------------- /log/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "module", 3 | "dependencies": { 4 | "nette-forms": "^3.3" 5 | }, 6 | "devDependencies": { 7 | "@nette/vite-plugin": "^1.0.1", 8 | "vite": "^6.3.5" 9 | }, 10 | "scripts": { 11 | "dev": "vite", 12 | "build": "vite build" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /phpstan.neon: -------------------------------------------------------------------------------- 1 | parameters: 2 | level: 5 3 | 4 | paths: 5 | - src 6 | - bin 7 | - tests 8 | 9 | includes: 10 | - vendor/phpstan/phpstan-nette/extension.neon 11 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | Create Your First Application! 2 | ============================== 3 | 4 | Get to know [Nette Framework](https://nette.org) while creating a simple blog with comments. Let's begin! 5 | 6 | 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. 7 | 8 | You can find the [complete tutorial on Nette website](https://doc.nette.org/en/quickstart/getting-started). 9 | 10 | Please install [full-featured IDE and all necessary plugins](https://doc.nette.org/en/best-practices/editors-and-tools), it will make you extremely efficient. 11 | 12 | This QuickStart was written for Nette Framework 3.2 and PHP 8.1 or newer. 13 | 14 | You can download the Nette Framework manually, but the recommended way of starting a new project is using [Composer](https://doc.nette.org/en/best-practices/composer). If you don't know the Composer, you should definitely start with that. It's a really simple and useful tool, check out [their documentation](https://getcomposer.org/doc/). 15 | 16 | With Composer, you can download and install the application skeleton known as Web Project including Nette Framework very easily. To do so, find your webroot directory (e.g. `/var/www` or `C:\InetPub`) in your command line and execute the following command: 17 | 18 | ```shell 19 | composer create-project nette/web-project nette-blog 20 | ``` 21 | 22 | Web Project will be downloaded into `nette-blog` directory. 23 | 24 | If you couldn't use Composer, [download](https://github.com/nette/web-project/archive/preloaded.zip) and extract the archive and copy it to the root directory of the webserver and rename to `nette-blog`. The entire framework is located in the `vendor` folder. 25 | 26 | If you're developing on macOS or Linux (or any other Unix based system), you need to [configure write privileges](https://doc.nette.org/en/troubleshooting#toc-setting-directory-permissions) to the webserver. 27 | 28 | 29 | The Welcome Page 30 | ---------------- 31 | 32 | 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: 33 | 34 | ``` 35 | http://localhost/nette-blog/www/ 36 | ``` 37 | 38 | and you should see the Nette Framework welcome page: 39 | 40 | ![](https://files.nette.org/git/doc/qs-welcome.webp) 41 | 42 | The application works and you can now start making changes to it. 43 | 44 | 45 | Web Project’s Content 46 | --------------------- 47 | 48 | Web Project has the following structure: 49 | 50 | ```pre 51 | nette-blog/ 52 | ├── app/ ← application directory 53 | │ ├── Core/ ← core classes, like router etc. 54 | │ ├── Presentation/ ← presenter classes & templates 55 | │ └── Bootstrap.php ← booting class Bootstrap 56 | ├── assets/ ← raw assets (SCSS, TypeScript, source images) 57 | ├── bin/ ← scripts for the command line 58 | ├── config/ ← configuration files 59 | ├── log/ ← error logs 60 | ├── temp/ ← temporary files, cache, … 61 | ├── vendor/ ← libraries installed by Composer 62 | │ └── autoload.php ← autoloading of libraries installed by Composer 63 | └── www/ ← public folder - the only place accessible from browser 64 | ├── assets/ ← compiled static files (CSS, JS, images, …) 65 | └── index.php ← initial file that launches the application 66 | ``` 67 | 68 | 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). 69 | 70 | 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](https://doc.nette.org/en/robot-loader) and sets up the [debugger](https://tracy.nette.org/) and [routes](https://doc.nette.org/en/application/routing). 71 | 72 | 73 | Cleanup 74 | ------- 75 | 76 | The Web Project contains a welcome page, which we can remove - feel free to delete the `app/Presentation/Home/default.latte` file and replace it with the text "Hello world!". 77 | 78 | 79 | ![](https://files.nette.org/git/doc/qs-hello.webp) 80 | 81 | 82 | Tracy (Debugger) 83 | ---------------- 84 | 85 | An extremely important tool for development is [a debugger called Tracy](https://tracy.nette.org/). Try to make some errors in your `app/Presentation/Home/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. 86 | 87 | ![](https://files.nette.org/git/doc/qs-tracy.webp) 88 | 89 | 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. 90 | 91 | ![](https://files.nette.org/git/doc/qs-tracybar.webp) 92 | 93 | 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: 94 | 95 | ```php .{file:app/Bootstrap.php} 96 | ... 97 | $this->configurator->setDebugMode(false); 98 | $this->configurator->enableTracy(__DIR__ . '/../log'); 99 | ... 100 | ``` 101 | 102 | After refreshing the web page, the red-screen page will be replaced with the user-friendly message: 103 | 104 | ![](https://files.nette.org/git/doc/qs-fatal.webp) 105 | 106 | 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`). 107 | 108 | Comment line `// $this->configurator->setDebugMode(false);` again. Tracy automatically enables development mode on `localhost` environment and disables it elsewhere. 109 | 110 | Now, we can fix the bug and continue designing our application. 111 | 112 | 113 | Read more 114 | --------- 115 | 116 | [The tutorial continue on the Nette website](https://doc.nette.org/en/quickstart/home-page). 117 | -------------------------------------------------------------------------------- /temp/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | import nette from '@nette/vite-plugin'; 3 | 4 | export default defineConfig({ 5 | plugins: [ 6 | nette({ 7 | input: 'main.js', 8 | }), 9 | ], 10 | 11 | build: { 12 | emptyOutDir: true, 13 | }, 14 | 15 | css: { 16 | devSourcemap: true, 17 | }, 18 | }); 19 | -------------------------------------------------------------------------------- /www/.htaccess: -------------------------------------------------------------------------------- 1 | # Apache configuration file (see https://httpd.apache.org/docs/current/mod/quickreference.html) 2 | 3 | # Allow access to all resources by default 4 | Require all granted 5 | 6 | # Disable directory listing for security reasons 7 | 8 | Options -Indexes 9 | 10 | 11 | # Enable pretty URLs (removing the need for "index.php" in the URL) 12 | 13 | RewriteEngine On 14 | 15 | # Uncomment the next line if you want to set the base URL for rewrites 16 | # RewriteBase / 17 | 18 | # Force usage of HTTPS (secure connection). Uncomment if you have SSL setup. 19 | # RewriteCond %{HTTPS} !on 20 | # RewriteRule .? https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L] 21 | 22 | # Permit requests to the '.well-known' directory (used for SSL verification and more) 23 | RewriteRule ^\.well-known/.* - [L] 24 | 25 | # Block access to hidden files (starting with a dot) and URLs resembling WordPress admin paths 26 | RewriteRule /\.|^\.|^wp-(login|admin|includes|content) - [F] 27 | 28 | # Return 404 for missing files with specific extensions (images, scripts, styles, archives) 29 | RewriteCond %{REQUEST_FILENAME} !-f 30 | RewriteRule \.(pdf|js|mjs|ico|gif|jpg|jpeg|png|webp|avif|svg|css|rar|zip|7z|tar\.gz|map|eot|ttf|otf|woff|woff2)$ - [L] 31 | 32 | # Front controller pattern - all requests are routed through index.php 33 | RewriteCond %{REQUEST_FILENAME} !-f 34 | RewriteCond %{REQUEST_FILENAME} !-d 35 | RewriteRule . index.php [L] 36 | 37 | 38 | # Enable gzip compression for text files 39 | 40 | AddOutputFilterByType DEFLATE text/html text/plain text/xml text/css application/javascript application/json application/xml application/rss+xml image/svg+xml 41 | 42 | -------------------------------------------------------------------------------- /www/assets/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | font: 16px/1.5 Georgia, Verdana, Arial; 3 | margin: 0 auto; 4 | width: 600px; 5 | color: #333; 6 | background-color: #fff; 7 | } 8 | 9 | /* Flash messages styling - typically used for notifications or alerts */ 10 | div.flash { 11 | color: black; 12 | background: #FFF9D7; 13 | border: 1px solid #E2C822; 14 | padding: 1em; 15 | margin: 1em 0; 16 | } 17 | 18 | /* Style for error links - used by presenters */ 19 | a[href^="#error:"] { 20 | background: red; 21 | color: white; 22 | } 23 | 24 | /* Form styles */ 25 | form th, form td { 26 | vertical-align: top; 27 | font-weight: normal; 28 | } 29 | 30 | form th { 31 | text-align: right; 32 | } 33 | 34 | /* Highlight required form fields */ 35 | form .required label { 36 | font-weight: bold; 37 | } 38 | 39 | /* Style for form error messages */ 40 | form .error { 41 | color: #D00; 42 | font-weight: bold; 43 | } 44 | -------------------------------------------------------------------------------- /www/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nette-examples/quickstart/4db1bf9a635c394b2fa0056fcfb27bc83e0364f1/www/favicon.ico -------------------------------------------------------------------------------- /www/index.php: -------------------------------------------------------------------------------- 1 | bootWebApplication(); 12 | // Start the application and handle the incoming request 13 | $application = $container->getByType(Nette\Application\Application::class); 14 | $application->run(); 15 | -------------------------------------------------------------------------------- /www/robots.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nette-examples/quickstart/4db1bf9a635c394b2fa0056fcfb27bc83e0364f1/www/robots.txt --------------------------------------------------------------------------------