Jump to content


  • Posts

  • Joined

  • Last visited

  • Days Won


Everything posted by MarkE

  1. Snap! The website is here but that is like the tip of the iceberg. Behind the scenes (i.e. in the admin) it does membership records, mailings, events management, news reporting, subscriptions etc. No credit card facility - we use direct debits courtesy of GoCardless instead - cheaper and more secure. Members access their details, book events etc. via the My NCOG page (no passwords - we use a one-time email token instead as we have a slightly senior group who are not reliable password users 😅). There are still quite a lot of rough edges, but it has been running for a couple of years now and does the business.
  2. I think this is an important topic and @pideluxe makes some useful suggestions. The subsequent discussion provides insights into what is attractive about PW and what people think is missing. I am a relative newcomer to PW and am only an amateur - in two senses: (a) as a retiree, I can do stuff for for friends and family without commercial pressures and (b) I have never had any training (and it shows 😉 ). What attracted me to PW was the ability to build custom apps integrated into a website and give others the ability to manage the content. Having used WP (aargh - customisation is a nightmare and the admin is really clunky), CodeIgniter (well-designed, but hard work!) and some simple CMS solutions, I wanted something that was easy to use and took out a lot of the hard coding work, but was flexible and capable. PW ticked all the boxes. As well as straight CMS, I have now built two quite complex apps - a club management system and a self-catering cottage management system. PW has been a great tool for the job. So the question is, why is it not more widely known and used? I think there are two issues, both of which have been mentioned by others: The project does appear to be very reliant on Ryan (albeit maybe less than previously owing to some of the excellent contributors to the ecosystem). This was not a problem for me (in fact a positive, because it has meant a clear vsion for the project), but is a negative point for some. My daughter, who is tech director of an e-marketing company, prefers WP even though it is technically inferior because of the greater assurance of continuity and the larger pool of people with relevant skills. The OP suggests some ways in which this perception could be changed, but I suspect that may not be possible without @ryan's active involvement. Over time, the user group has become more technical so that the appeal is narrowing. In some ways, this is a consequence of PW's success in enabling some pretty sophisticated sites/apps - this has attracted technophiles whose needs have further driven it in this direction. This has happened without any active decision on anyone's part. Some may feel very comfortable with this and think that it is a perfectly viable niche. I am not so sure - the risk is that the ease-of-use aspects are increasingly downplayed so that ultimately the comparison is with, say, Laravel, rather than WP. The bottom line is that PW's initial attraction of being both a 'simple' CMS and a sophisticated app-building tool risk it ending up being between a rock and a hard place. That would be a great shame because IMHO it is better, technically, than the competition. You may infer from my comments above that I think that part of the 'problem' is that the project is largely technically-driven - inevitable given the nature of those involved. This is not a critcism, just a fact of life. But perhaps the community needs to get more input from outside and think about PW's marketplace more broadly. FWIW, I agree with many of the suggestions above, including Pretty much all of @OllieMackJames's initial post. BTW, this one - is one of the few things that actually irritates me about PW, which is why I am trying to do something about it (see https://processwire.com/talk/topic/25307-oh-no-not-another-migration-module/, but I really think something like this should be in the core).
  4. Thanks. I realise that - just saves a bit of typing with big templates 🙂
  5. Fair point and it's true that in practice I rarely change names, but I just feel more comfortable with (a) them all being declared at the top of the class so they can be changed there if necessary and (b) the IDE recognising the names and prompting, rather than relying on 'magic methods'. Either approach achieves that. Really good example. Thanks a lot. Food for thought!
  6. To reduce maintenance effort, it seems to me to be important that there is a clear and manageable interface between names in the PW database and those in your PHP code. But what is the best way of achieving this? Firstly, I think the use of custom Page classes helps enormously, but then what? Two PW gurus to whom I usually look for inspiration seem to have different approaches. @Robin S appears to favour defining properties above the class, as is exemplified in his AutoTemplateStubs module. Alternatively, @bernhard uses constants to define fields (see here for example). Both work but have pros and cons. The first approach seems more straightforward and fits naturally into the normal PHP object->property syntax and PW selector syntax, but if you change a fieldname in the database, you have to get your IDE to refactor it in the code (PhpStorm seems to handle this fine - refactor the @property definition and all the accessed properties get changed). The second approach limits your syntax to an array-style, but means you just change the constant value if the field name changes - no refactoring required. Any other views on this, or better ideas?
  7. Actually it's not a PhpStorm issue at all. Just me being a bit dim. The @property definitions are in the Page class in AutoTemplateStubs, which extends Page. My code is in an identically-named class which extends DefaultPage, so I guess PhPStorm assumes they are different classes. If I copy the @property definitions into my DefaultPage extension then PhpStorm gets it OK.
  8. I've addressed that here. Hopefully I'll get to release some code shortly 😉
  9. Yeah - hooks need to be bypassed if null page - sorted now, but one to remember.
  10. In my setup that works with init, but not with ready (Error: WireDataDB sourceID must be greater than 0).
  11. Just wondering what happens if you implement module in a Page class......
  12. There is a subtle but important difference between the above and your suggestion. Actually in this case, I needed $this != $p because I did not want the hook to run on any page other than the current instance. Generally, to filter the page classes, your suggestion is the right one. I am now doing the filtering with (template=...) as per my earlier post, so the filter in the method is no longer needed. However, as I have now discovered, for my use case, I do still need to check that the save being hooked is of the current instance - which can either be done by $this != $p or by using $p thoughout in the hook method (not sure which is better...) . Good question! I am using the (before) hook to validate fields and so Pages::save() is right, I think.
  13. OK @bernhard - looks like our posts crossed! _ I think I have it working, but your comments are helpful. I was tracking the other (coincidental) thread. Just to re-emphasise, __construct() is definitely the wrong place for this!!
  14. OK - I think I may have this sorted: The hooks must go in ___loaded(), __construct() is too early. $this->addHookBefore('Pages::save(template=DbMigration)', $this, 'beforeSave'); seems to work OK
  15. That's where they are. I am also trying $this->addHookAfter('Pages::save(template=TemplateName)', $this, 'afterSave'); but can't get it to work at the moment.
  16. Snap!! I find it is the case for both direct and subclasses Nice plan - quite neat and maybe better than using ___loaded() - but I still find the called method operates on all pages, so I need to filter with protected function beforeSave(HookEvent $event) { $p = $event->arguments(0); if ($this != $p) return; It may be that my use case is rather different and that, in other circumstances, your solution works. In my case a button on the page executes a method which saves pages of a different class, but these still get caught by the hook. EDIT: I find that @bernhard's solution has the same issue in my use case. The problem does not 'appear' to exist when using ready.php, because you naturally do $p = $event->arguments(0); there anyway and test for template before passing to the class method.
  17. I'm sure I read something about this recently, but I can't find it ... (is there a way of bookmarking forum posts?) I'm trying to move my page hooks out of ready.php into Page Classes, which I think are tremendous and use extensively. But what is the best way to do it? ready() doesn't trigger automatically. I tried public function ___loaded() { $this->ready(); } (thanks to @Robin S) and this does trigger it. However, if I do, say public function ready() { $this->addHookAfter('Pages::save', $this, 'afterSave'); $this->addHookBefore('Pages::save', $this, 'beforeSave'); } then the hook methods get triggered by any page save, not just of instances of this class. I solved this by adding a breakout at the start of the method: protected function beforeSave(HookEvent $event) { $p = $event->arguments(0); if ($this != $p) return; ...... but this seems a bit inelegant. Any better ideas?
  18. I quite agree - I use custom page classes for practically every template. One question, though not entirely specific to RM: how do you deal with functions in init.php that are used by multiple classes? In my dbMigrate module in the 'parallel universe' of UI development 😉 I use some custom methods and functions that are in my DefaultPage.php and init.php because I use them more widely than just in the module. Of course, they can be readily found with the IDE (although it is an issue with packaging the module), but your comment about init.php made me think that I am missing a trick.
  19. I've reworked this slightly, based on the helpful suggestion of @Kiwi Chris. The individual migration items are now entered in a repeater field and so the sequence can take into account any dependencies. Each item can be either 'new', 'changed' or 'removed'. When a migration is uninstalled, the sequence is automatically reversed and 'new' items are changed to 'removed' and vice versa. Example below shows a migration testing a page reference dependency (it works!). This is the appearance in the source database (pre-export): If you click 'Preview' you see what changes are proposed to export (see below). This feature also operates to review (in the target database) what changes will happen on install or uninstall - or, if the install has failed, what changes remain (either by modifying the migration or applying a manual fix if all else fails). Export is shown as 'no object' in the above, because the migration has not yet been exported. I think this all seems to work as designed, but am grateful for any futher thoughts on the design. I will now work on tidying the code a bit and doing a bit of documentation. There may still be a few bugs. There are some at the moment that I can't pin down but they are cosmetic rather than fundamental - I think I may need some help from those who understand the inner workings of PW better ( @adrian ?). Promises of help will encourage me to get on and release some code 😉 Meanwhile, I am happy to answer any further questions.
  20. Originally I was using select fields and lookups, but as the objects are not necessarily in the current database (and it depends on whether you are in a source or target environment), this made things troublesome and inconsistent, so I reverted to plain text. Your general suggestion of item by item ordering is interesting and I will consider that, thanks.
  21. The sequence is: remove (pages > templates > fields); add/change (fields > templates > pages). Within each type (e.g. pages), the sequence is in the order listed in the entry. I think this caters for most situations - e.g. a new page select field followed by a changed template, followed by pages in the right dependency order - but it may not cover everything (particularly making the uninstall happen correctly too). In mitigation, unsuccessful migrations can be reviewed via the previews (see example below) and more complex migrations can be broken into smaller migrations to better handle dependencies (I had thought of providing dependency linkage between migrations, but at the moment I think that adds too much complexity). Pic below shows an example preview with one 're-purposed' field and one new one - this can be reviewed before implementation and also afterwards if the install was not complete. I will take a closer look at that to see if it helps any. Quite! IMHO it is the main area where PW is lacking, particularly given that it is such a good tool for heavy-duty apps as well as just websites - a point that has been well made by @Kiwi Chris. I was surprised and a bit disappointed by how little attention this has had in terms of core modules. Not sure if I need to loop round like this (and also as @adrian indicates). Further testing may indicate that it is necessary. Well, they could but the tools are not complete (e.g. fields does not handle select options and pages does not exist except 'in development'); for anything but the simplest migration, you have to do several manual steps in the right order (coupled with working out what didn't implement correctly) - this can be a real pain if you are testing several times then needing to implement on the live version and can run a real risk of making mistakes when doing it for the n'th time; there is no documentation of what you have done; there is no ability to uninstall other than to repeat the manual steps correctly in reverse order. For very simple migrations (e.g. one changed template), then it is a feasible approach. I wanted something that installs fast and consistently every time - particularly when it is interdependent with changed code and I want to minimise downtime. Also, it is not just a case of 'not wanting to learn'. Code-based migrations need familiarity through frequent use and work best in the context of code-based app development where the same comment applies. I'll do a bit more testing and try and cover some of the points made above, then do this. Meanwhile, the main task is to refactor and restructure the code which is a truly horrible lot of spaghetti at the moment. If this is to go any further, I will then need some help, particularly with turning it into a more 'professional' module, as my PHP (particularly OOP) skills are limited and I have never previously done anything beyond simple Process modules .
  22. Well, yes and no. Two migration modules already exist in ProcessWire, but neither suited my needs: “Migrations” by @LostKobrakai seems effective but quite onerous to use and has been deprecated in favour of “RockMigrations” RockMigrations by @bernhard is simpler and has a nice declarative method: migrate(). However, it is ideally suited to “headless” development, where the API is used in preference to the Admin UI. This is great for professional PW developers, but for occasional developers like me, it is much easier to use the UI rather than just the API. In addition there @adrian's ProcessMigrator which is designed for migrating page trees. Concept I wanted something to achieve the following: To allow development to take place (in a separate environment on a copy of the live database, or on a test database with the same structure) using the Admin UI. When finished, to permit a declarative approach to defining the migration and implementing it (again in the UI). To allow testing of the migration in a test environment on a copy of the live database. To allow roll-back of a migration if installation causes problems (ideally while testing rather than after implementation!). To provide a record of changes applied. Although not originally intended, the module I developed also allows the selective reversion of parts of the database by exporting migration data from a backup copy. Also, if changes are made directly on the live system (presumably simple, low-risk mods – although not best practice), it allows reverse migration to the development system in a similar fashion. I should emphasise that what I have built is more of a 'proof of concept' than a fully-fledged module. The code is pretty hacky and uses some stuff outside of the module itself. Lots of validation is missing. However, I have used it successfully in a number of small tests and a medium-sized live migration. If there is sufficient interest, I will tidy the code and make it available, but it would still need input from better coders and PW-savants than me to make it into something more widely usable. EDIT: Please note that the module has moved on a bit from this original post - the design has changed somewhat to make it more robust and flexible and additional features have been added. Please see the help file for full details. I still consider it to be at alpha stage, however, so use with care - test before making migrations and always take backups first. Design The module has the following principal components: A PW module “ProcessMigrateData”, bundled with a bootstrap migration in the same ProcessMigrateData folder, to be placed in the site/modules folder; A Page Class “MigrationPage” to be placed in the site/classes folder; Php files migrationActions.php and migrationControl.php to be placed in the site/templates/RuntimeMarkup folder (and migrationActions.js to be placed in site/templates/RuntimeMarkup/scripts). There are also a methods which need to be put in class DefaultPage and a functions in the init.php file. The module requires the FieldtypeRuntimeMarkup module. Migration definitions are held in .json files in the ProcessMigrateData/migrations/{migration name} folder (I might move this). This folder contains up to 2 sub-folders - “new” and “old” which each contain a file called a migration.json file, which defines the scope of the migration in terms of fields, templates and pages, and also one or more of fields.json, templates.json, pages.json and remove.json. The first 3 of these files contain the field, template and file definitions within the migration scope and the remove.json file simply lists fields, templates and pages to be removed. These migration files are mirrored by pages of template “Migration” under a parent /migrations/ of template “Migrations”. The mirroring happens in two ways: If a new migration is created in the module (from the Setup -> DB Migration menu), then initially no json files exist. The json files are created, after the scope of the migration is defined on the page, by running “Export Data” from the eponymous button. If json files exist, but there is no matching migration page, then the latter is created by the module on accessing the DB Migration admin page. In this case, we are in the “target” database so there is no “Export Data” button, but instead “Install” and/or “Uninstall” buttons. Migrations therefore either view the current environment as a “source” (type 1) or a “target” (type 2). Installation The module creates templates called Migration and Migrations and a page below the root named ‘migrations’. Open the admin page “Setup -> DB Migration” to create a migration. One (“bootstrap” is already installed) and cannot be modified. The pic below illustrates the DB Migrations page in the source environment. The status of a migration (as a source page) can be ‘pending’ or ‘exported’. ‘Pending’ means either that the migration data have not yet been exported or that the current export files differ from the source database. On opening this page in the target environment, the individual Migration pages (type 2) are created from the definitions in their respective /new/migration.json file. The pic below illustrates the DB Migrations page in the target environment. In a target environment, a migration status can usually be ‘indeterminate’, ‘installed’ or ‘uninstalled’. ‘Indeterminate’ means either that the migration has not yet been installed (so no ‘old’ files containing the uninstall definition exist yet) or that the current state matches neither the ‘new’ or the ‘old’ state. ‘Installed’ means that the current state matches the ‘new’ definition and ‘uninstalled’ means that it matches the ‘old’ definition (i.e. it has been actively uninstalled rather than not yet installed). When carrying out development work, you keep a note of what fields, templates and pages you have added, changed or removed. The module does not track this – it is a declarative approach, not a macro recorder. Also, it does not handle other components such as Hanna codes and Formbuilder forms. These come equipped with their own export/import functions. You can update a migration page as you go along, rather than keep a separate note of changed components. The migration page also allows you to document the migration and add any number of “snippets”. These snippets do not do anything, but can be a convenient place to store (for example) Hanna code exports for pasting into the target environment and help to make the page a comprehensive record of the migration. See example below: Note that migration pages just define the scope of the migration. It is entirely feasible for other parts of the dev database to be changed which are outside this scope and which will therefore not be migrated. After sync'ing code files to the target environment, the new migration will be listed on the setup page. On the migration page, in the target environment, there are “preview” buttons to see what changes will be implemented. The migration can then be 'installed'. See example of the migration page in ‘installation’ mode below: That's the gist of it, but inevitably there are complications. Happy to discuss and share further if there is interest in this.
  23. Well, no different, in a way, as it uses all that code. It just provides a UI to pull it all together and automate all the export/import in the right order. More later...
  24. Easier said than done. I'll see what I can do. I can see that if you are using RockMigrations from the start, then the issue does not arise as all fieldnames will be lowercase. My situation is a bit different as I only use PW occasionally, and find the Admin UI, rather than a 'headless' approach, works better for me. However, I find migrations a pain. Consequently, I have started working on a UI interface module for your module, which is coming along quite nicely and which I will provide more details on once I have it in a reasonable state. It generates json files from the database which are then used as input to the migration, so it uses the existing fieldnames, which may not be lower case. Having run into this, as well as a few other problems with using RockMigrations in this way, I have switched to using the core code directly as I appreciate that RockMigrations was not designed with this in mind. For fields, I am using a modified copy of ProcessFieldExportImport::processImport() (although that has problems with options fields), for templates, I call ProcessTemplatesExportImport::setImportData() and am just using the standard API for pages. However, there are attractions in using the RockMigrations methods so I may return to this once all the UI stuff is working, particularly if you think it is a good idea.
  • Create New...